package assembler;

	import java.util.*;
	import javax.swing.*;

	import java.awt.*;
	import java.awt.event.*;
	import java.io.*;

	import javax.swing.filechooser.FileFilter;

	import javax.swing.JFrame;

	public class AssemblerGUI implements ActionListener
	{
		/**
		 * Start the assembler GUI.
		 *
		 * @param args Commandline arguments.
		 */
		public static void main(String[] args)
		{
			// Set the loop and feel of the application to the system default.
			try
			{
				UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
			}
			catch (Exception e) {}

			// Run our Assembler GUI.
			new AssemblerGUI(args);
		}

		// Main window frame.
		private JFrame frame;

		// Menu Items
		private JMenuItem openMenuItem;
		private JMenuItem testMenuItem;
		private JMenuItem exitMenuItem;
		private JMenuItem saveSymbolTable;
		private JMenuItem saveCodeListing;
		private JMenuItem saveMCSOutput;
		private JMenuItem saveXESOutput;
		private JMenuItem saveMIFOutput;
		private JMenuItem clearTextAreaMenuItem;
		private JRadioButtonMenuItem blModeMenuItem;
		private JRadioButtonMenuItem extModeMenuItem;
		private JCheckBoxMenuItem printSymbolTable;
		private JCheckBoxMenuItem printCodeListing;
		private JCheckBoxMenuItem printMCSOutput;
		private JCheckBoxMenuItem printXESOutput;
		private JCheckBoxMenuItem printMIFOutput;

		// TextArea Setup
		private JTextArea textArea;

		// Assembler object for this class.
		private Assembler assembler;

		/**
		 * Setup the GUI and set the window visible.
		 *
		 * @param args Commandline arguments.
		 */
		public AssemblerGUI(String[] args)
		{
	        // Setup the main frame.
			frame = new JFrame("CR16 Assembler");
	        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	        Container contentPane = frame.getContentPane();
	        contentPane.setLayout(new BorderLayout());
	        frame.setPreferredSize(new Dimension(640, 480));

	        // Setup the menu bar.
	        JMenuBar menuBar = new JMenuBar();
	        frame.setJMenuBar(menuBar);
	        JMenu fileMenu = new JMenu("File");
	        fileMenu.setMnemonic(KeyEvent.VK_F);
	        menuBar.add(fileMenu);
	        openMenuItem = new JMenuItem("Open");
	        openMenuItem.addActionListener(this);
	        openMenuItem.setMnemonic(KeyEvent.VK_O);
	        openMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
	        exitMenuItem = new JMenuItem("Quit");
	        exitMenuItem.addActionListener(this);
	        exitMenuItem.setMnemonic(KeyEvent.VK_Q);
	        exitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
	        testMenuItem = new JMenuItem("Test");
	        testMenuItem.addActionListener(this);
	        testMenuItem.setMnemonic(KeyEvent.VK_T);
	        testMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, ActionEvent.CTRL_MASK));
	        saveSymbolTable = new JMenuItem("Save Symbol Table As");
	        saveSymbolTable.addActionListener(this);
	        saveSymbolTable.setEnabled(false);
	        saveCodeListing = new JMenuItem("Save Code Listing As");
	        saveCodeListing.addActionListener(this);
	        saveCodeListing.setEnabled(false);
	        saveMCSOutput = new JMenuItem("Save MCS Output As");
	        saveMCSOutput.addActionListener(this);
	        saveMCSOutput.setEnabled(false);
	        saveMCSOutput.setMnemonic(KeyEvent.VK_M);
	        saveMCSOutput.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.CTRL_MASK));
	        saveXESOutput = new JMenuItem("Save XES Output As");
	        saveXESOutput.addActionListener(this);
	        saveXESOutput.setEnabled(false);
	        saveMIFOutput = new JMenuItem("Save MIF Output As");
	        saveMIFOutput.addActionListener(this);
	        saveMIFOutput.setEnabled(false);
	        saveMIFOutput.setMnemonic(KeyEvent.VK_S);
	        saveMIFOutput.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
	        fileMenu.add(openMenuItem);
	        //fileMenu.addSeparator(); fileMenu.add(testMenuItem);
	        fileMenu.addSeparator();
	        fileMenu.add(saveSymbolTable);
	        fileMenu.add(saveCodeListing);
	        fileMenu.add(saveMCSOutput);
	        fileMenu.add(saveXESOutput);
	        fileMenu.add(saveMIFOutput);
	        fileMenu.addSeparator();
	        fileMenu.add(exitMenuItem);

	        // Setup the text area.
	        textArea = new JTextArea();
			textArea.setText("");
			output("CR16 Assembler Ready\n\n");
			textArea.setFont(new Font("Courier New", Font.PLAIN, 12));
			textArea.setEditable(false);
	        JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	        scrollPane.setWheelScrollingEnabled(true);
	        scrollPane.setBorder(null);
	        contentPane.add(scrollPane, BorderLayout.CENTER);

	        // Setup the Assembler and it's mode choosing:
	        assembler = new Assembler();
	        int defaultCompliance = assembler.getCompliance();
	        JMenu modeMenu = new JMenu("Assembler Compliance");
	        modeMenu.setMnemonic(KeyEvent.VK_A);
	        menuBar.add(modeMenu);
	        ButtonGroup radioButtonGroup = new ButtonGroup();
	        blModeMenuItem = new JRadioButtonMenuItem("Base-line Compliance");
	        blModeMenuItem.setMnemonic(KeyEvent.VK_B);
	        blModeMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, ActionEvent.CTRL_MASK));
	        radioButtonGroup.add(blModeMenuItem);
	        blModeMenuItem.addActionListener(this);
	        modeMenu.add(blModeMenuItem);
	        if (defaultCompliance == Assembler.BL_COMPLIANCE)
	        	blModeMenuItem.setSelected(true);
	        extModeMenuItem = new JRadioButtonMenuItem("Extended Compliance");
	        extModeMenuItem.setMnemonic(KeyEvent.VK_E);
	        extModeMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.CTRL_MASK));
	        radioButtonGroup.add(extModeMenuItem);
	        extModeMenuItem.addActionListener(this);
	        modeMenu.add(extModeMenuItem);
	        if (defaultCompliance == Assembler.EXT_COMPLIANCE)
	        	extModeMenuItem.setSelected(true);

	        // Setup the view menu.
	        JMenu viewMenu = new JMenu("View");
	        viewMenu.setMnemonic(KeyEvent.VK_V);
	        menuBar.add(viewMenu, 1);
	        clearTextAreaMenuItem = new JMenuItem("Clear Text Area");
	        clearTextAreaMenuItem.setMnemonic(KeyEvent.VK_C);
	        clearTextAreaMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.ALT_MASK));
	        clearTextAreaMenuItem.addActionListener(this);
	        printSymbolTable = new JCheckBoxMenuItem("Print Symbol Table");
	        printSymbolTable.setMnemonic(KeyEvent.VK_S);
	        printSymbolTable.setSelected(true);
	        printCodeListing = new JCheckBoxMenuItem("Print Code Listing");
	        printCodeListing.setMnemonic(KeyEvent.VK_L);
	        printCodeListing.setSelected(true);
	        printMCSOutput = new JCheckBoxMenuItem("Print MCS Output");
	        printMCSOutput.setMnemonic(KeyEvent.VK_C);
	        printMCSOutput.setSelected(false);
	        printXESOutput = new JCheckBoxMenuItem("Print XES Output");
	        printXESOutput.setMnemonic(KeyEvent.VK_X);
	        printXESOutput.setSelected(false);
	        printMIFOutput = new JCheckBoxMenuItem("Print MIF Output");
	        printMIFOutput.setMnemonic(KeyEvent.VK_M);
	        printMIFOutput.setSelected(false);
	        viewMenu.add(clearTextAreaMenuItem);
	        viewMenu.addSeparator();
	        viewMenu.add(printSymbolTable);
	        viewMenu.add(printCodeListing);
	        viewMenu.addSeparator();
	        viewMenu.add(printMCSOutput);
	        viewMenu.add(printXESOutput);
	        viewMenu.add(printMIFOutput);

	        // Make the frame visible.
	        frame.pack();
	        frame.setVisible(true);
		}

		/**
		 * Handle button presses from the Assembler GUI.
		 *
		 * @param event Action event.
		 */
		public void actionPerformed(ActionEvent event)
		{
			if (event.getSource() == openMenuItem)
			{
			    JFileChooser chooser = new JFileChooser(System.getProperty("user.dir"));
			    chooser.setFileFilter(new FileFilter() {
				    public boolean accept(File f)
				    {
				    	if (f == null) return false;
				    	else if (f.isDirectory()) return true;
				    	else if (f.getName().toLowerCase().endsWith(".asm")) return true;
				    	else if (f.getName().toLowerCase().endsWith(".txt")) return true;
				    	else return false;
				    }
				    public String getDescription()
				    {
				    	return "CR16 Assembly Code (*.asm/*.txt)";
				    }
			    });

			    int returnVal = chooser.showOpenDialog(frame);
			    if(returnVal == JFileChooser.APPROVE_OPTION)
			    {
			    	File file = chooser.getSelectedFile();
			    	if (file != null)
			    		assembleFile(file);
			    }
			}
			else if (event.getSource() == testMenuItem)
			{
				// Just a menu item to test something with input.
				//String result = JOptionPane.showInputDialog(null);
			}
			else if (event.getSource() == exitMenuItem)
			{
				System.exit(0);
			}
			else if (event.getSource() == clearTextAreaMenuItem)
			{
				textArea.setText("");
			}
			else if (event.getSource() == saveSymbolTable)
			{
				saveSymbolTable();
			}
			else if (event.getSource() == saveCodeListing)
			{
				saveCodeListing();
			}
			else if (event.getSource() == saveMCSOutput)
			{
				saveMCSOutput();
			}
			else if (event.getSource() == saveXESOutput)
			{
				saveXESOutput();
			}
			else if (event.getSource() == saveMIFOutput)
			{
				saveMIFOutput();
			}
			else if (event.getSource() == blModeMenuItem)
			{
				assembler.setCompliance(Assembler.BL_COMPLIANCE);
				output("\nAssembler compliance setting: Base-Line\n");
			}
			else if (event.getSource() == extModeMenuItem)
			{
				assembler.setCompliance(Assembler.EXT_COMPLIANCE);
				output("\nAssembler compliance setting: Extended\n");
			}
		}

		/**
		 * Use the assembler methods to assemble the file, param file.
		 *
		 * @param file	The file to assemble.
		 */
		private void assembleFile(File file)
		{
			saveSymbolTable.setEnabled(false);
			saveCodeListing.setEnabled(false);
			saveMCSOutput.setEnabled(false);

			Iterator<String> it = null;

			// Load the selected file.
			if (!assembler.loadAssemblyFile(file))
			{
				printErrors();
				return;
			}
			else
			{
				output("Loaded file: " + file.getAbsolutePath() + "\n\n");
			}

			// Print out the symbol table.
			Map<String, Integer> symbolTable = assembler.getSymbolTable();
			if (printErrors())
			{
				output("Please fix the errors in the assembly file and try again.\n\n");
				return;
			}
			if (printSymbolTable.isSelected())
			{
				output("Symbol table:");
				it = symbolTable.keySet().iterator();
				while (it.hasNext())
				{
					String label = it.next();
					int address = symbolTable.get(label);
					output("\t" + label + " = " + Assembler.convertToHexString(address, 4));
				}
			}
			else
				output("\n\nSymbol table generated, but not printed.");
			saveSymbolTable.setEnabled(true);

			// Print out the code listing.
			ArrayList<String> codeListing = assembler.getCodeListing();
			if (printErrors())
			{
				output("Please fix the errors in the assembly file and try again.\n\n");
				return;
			}
			if (printCodeListing.isSelected())
			{
				output("\n\nCode Listing:");
				it = codeListing.iterator();
				while (it.hasNext())
					output(it.next());
			}
			else
				output("\n\nCode listing generated, but not printed.");
			saveCodeListing.setEnabled(true);

			// Print the MCSOutput
			ArrayList<String> MCSOutput = assembler.getMCSOutput();
			if (printErrors())
			{
				output("Please fix the errors in the assembly file and try again.\n\n");
				return;
			}
			if (printMCSOutput.isSelected())
			{
				if (MCSOutput != null)
				{
					output("\n\nMCS Output:");
					it = MCSOutput.iterator();
					while (it.hasNext())
						output("\t" + it.next());
				}
			}
			else
				output("\n\nMCS output generated, but not printed.");
			saveMCSOutput.setEnabled(true);

			// Print the XESOutput
			ArrayList<String> XESOutput = assembler.getXESOutput();
			if (printErrors())
			{
				output("Please fix the errors in the assembly file and try again.\n\n");
				return;
			}
			if (printXESOutput.isSelected())
			{
				if (XESOutput != null)
				{
					output("\n\nXES Output:");
					it = XESOutput.iterator();
					while (it.hasNext())
						output("\t" + it.next());
				}
			}
			else
				output("\n\nXES output generated, but not printed.");
			saveXESOutput.setEnabled(true);

			// Print the MIFOutput
			ArrayList<String> MIFOutput = assembler.getMIFOutput();
			if(printErrors())
			{
				output("Please fix the errors in the assembly file and try again.\n\n");
				return;
			}
			if (printMIFOutput.isSelected())
			{
				if(MIFOutput != null)
				{
					output("\n\nMIF Output:");
					it = MIFOutput.iterator();
					while(it.hasNext())
						output("\t" + it.next());
				}
			}
		}

		/**
		 * Pops up a chooser dialog for the filename, and then saves the symbol
		 * table output to that file.
		 */
		private void saveSymbolTable()
		{
			Map<String, Integer> symbolTable = assembler.getSymbolTable();
			if (symbolTable == null)
			{
				JOptionPane.showMessageDialog(frame, "Symbol table cannot be created.");
				return;
			}

		    JFileChooser chooser = new JFileChooser();

		    int returnVal = chooser.showSaveDialog(frame);
		    if(returnVal == JFileChooser.APPROVE_OPTION)
		    {
		    	File file = chooser.getSelectedFile();
		    	if (file == null) return;

		    	if (file.exists())
		    	{
		    		int choice = JOptionPane.showConfirmDialog(frame, "Do you want to overwrite the contents of:\n" + file.getAbsolutePath(), "File Exists", JOptionPane.YES_NO_OPTION);
		    		if (choice == JOptionPane.NO_OPTION)
		    			return;
		    	}

	    		try
	    		{
	    			BufferedWriter out = new BufferedWriter(new FileWriter(file));

	    			Iterator<String> it = symbolTable.keySet().iterator();
	    			while (it.hasNext())
	    			{
	    				String label = it.next();
	    				int address = symbolTable.get(label);
	    				out.write(label + " = " + Assembler.convertToHexString(address, 4));
						out.newLine();
	    			}

	    			out.close();

	    			JOptionPane.showMessageDialog(frame, "Symbol table saved to the file:\n" + file.getAbsolutePath());
	    		}
	    		catch (IOException e)
	    		{
	    			JOptionPane.showMessageDialog(frame, "Unable to open the file:\n" + file.getAbsolutePath());
	    		}
		    }
		}

		/**
		 * Pops up a chooser dialog for the filename, and then saves the code
		 * listing output to that file.
		 */
		private void saveCodeListing()
		{
			ArrayList<String> codeListing = assembler.getCodeListing();
			if (codeListing == null)
			{
				JOptionPane.showMessageDialog(frame, "Code listing cannot be created.");
				return;
			}

		    JFileChooser chooser = new JFileChooser();

		    int returnVal = chooser.showSaveDialog(frame);
		    if(returnVal == JFileChooser.APPROVE_OPTION)
		    {
		    	File file = chooser.getSelectedFile();
		    	if (file == null) return;

		    	if (file.exists())
		    	{
		    		int choice = JOptionPane.showConfirmDialog(frame, "Do you want to overwrite the contents of:\n" + file.getAbsolutePath(), "File Exists", JOptionPane.YES_NO_OPTION);
		    		if (choice == JOptionPane.NO_OPTION)
		    			return;
		    	}

	    		try
	    		{
	    			BufferedWriter out = new BufferedWriter(new FileWriter(file));

	    			Iterator<String> it = codeListing.iterator();
	    			while (it.hasNext())
	    			{
	    				String string = it.next();
	    				out.write(string.replaceAll("\n", ""));
						out.newLine();
	    			}

	    			out.close();

	    			JOptionPane.showMessageDialog(frame, "Code listing saved to the file:\n" + file.getAbsolutePath());
	    		}
	    		catch (IOException e)
	    		{
	    			JOptionPane.showMessageDialog(frame, "Unable to open the file:\n" + file.getAbsolutePath());
	    		}
		    }
		}

		/**
		 * Pops up a chooser dialog for the filename, and then saves the MCS
		 * output to that file.
		 */
		private void saveMCSOutput()
		{
			ArrayList<String> MCS = assembler.getMCSOutput();
			if (MCS == null)
			{
				JOptionPane.showMessageDialog(frame, "MCS output cannot be created.");
				return;
			}

		    JFileChooser chooser = new JFileChooser();
		    chooser.setFileFilter(new FileFilter() {
			    public boolean accept(File f)
			    {
			    	if (f == null) return false;
			    	else if (f.isDirectory()) return true;
			    	else if (f.getName().toLowerCase().endsWith(".mcs")) return true;
			    	else if (f.getName().toLowerCase().endsWith(".hex")) return true;
			    	else return false;
			    }
			    public String getDescription()
			    {
			    	return "HEX Output (*.mcs/*.hex)";
			    }
		    });

		    int returnVal = chooser.showSaveDialog(frame);
		    if(returnVal == JFileChooser.APPROVE_OPTION)
		    {
		    	File file = chooser.getSelectedFile();
		    	if (file == null) return;

		    	if (file.exists())
		    	{
		    		int choice = JOptionPane.showConfirmDialog(frame, "Do you want to overwrite the contents of:\n" + file.getAbsolutePath(), "File Exists", JOptionPane.YES_NO_OPTION);
		    		if (choice == JOptionPane.NO_OPTION)
		    			return;
		    	}

	    		try
	    		{
	    			BufferedWriter out = new BufferedWriter(new FileWriter(file));

	    			Iterator<String> it = MCS.iterator();
	    			while (it.hasNext())
	    			{
	    				String string = it.next();
	    				out.write(string.replaceAll("\n", ""));
						out.write("\r\n");
	    			}

	    			out.close();

	    			JOptionPane.showMessageDialog(frame, "MCS output saved to the file:\n" + file.getAbsolutePath());
	    		}
	    		catch (IOException e)
	    		{
	    			JOptionPane.showMessageDialog(frame, "Unable to open the file:\n" + file.getAbsolutePath());
	    		}
		    }
		}

		/**
		 * Pops up a chooser dialog for the filename, and then saves the XES
		 * output to that file.
		 */
		private void saveXESOutput()
		{
			ArrayList<String> MCS = assembler.getXESOutput();
			if (MCS == null)
			{
				JOptionPane.showMessageDialog(frame, "XES output cannot be created.");
				return;
			}

		    JFileChooser chooser = new JFileChooser();
		    chooser.setFileFilter(new FileFilter() {
			    public boolean accept(File f)
			    {
			    	if (f == null) return false;
			    	else if (f.isDirectory()) return true;
			    	else if (f.getName().toLowerCase().endsWith(".xes")) return true;
			    	else return false;
			    }
			    public String getDescription()
			    {
			    	return "XES Output (*.xes)";
			    }
		    });

		    int returnVal = chooser.showSaveDialog(frame);
		    if(returnVal == JFileChooser.APPROVE_OPTION)
		    {
		    	File file = chooser.getSelectedFile();
		    	if (file == null) return;

		    	if (file.exists())
		    	{
		    		int choice = JOptionPane.showConfirmDialog(frame, "Do you want to overwrite the contents of:\n" + file.getAbsolutePath(), "File Exists", JOptionPane.YES_NO_OPTION);
		    		if (choice == JOptionPane.NO_OPTION)
		    			return;
		    	}

	    		try
	    		{
	    			BufferedWriter out = new BufferedWriter(new FileWriter(file));

	    			Iterator<String> it = MCS.iterator();
	    			while (it.hasNext())
	    			{
	    				String string = it.next();
	    				out.write(string.replaceAll("\n", ""));
						out.write("\r\n");
	    			}

	    			out.close();

	    			JOptionPane.showMessageDialog(frame, "XES output saved to the file:\n" + file.getAbsolutePath());
	    		}
	    		catch (IOException e)
	    		{
	    			JOptionPane.showMessageDialog(frame, "Unable to open the file:\n" + file.getAbsolutePath());
	    		}
		    }
		}

		/**
		 * Pops up a chooser dialog for the filename, and then saves the XES
		 * output to that file.
		 */
		private void saveMIFOutput()
		{
			ArrayList<String> MIF = assembler.getMIFOutput();
			if (MIF == null)
			{
				JOptionPane.showMessageDialog(frame, "MIF output cannot be created.");
				return;
			}

		    JFileChooser chooser = new JFileChooser();
		    chooser.setFileFilter(new FileFilter() {
			    public boolean accept(File f)
			    {
			    	if (f == null) return false;
			    	else if (f.isDirectory()) return true;
			    	else if (f.getName().toLowerCase().endsWith(".mif")) return true;
			    	else return false;
			    }
			    public String getDescription()
			    {
			    	return "MIF Output (*.mif)";
			    }
		    });

		    int returnVal = chooser.showSaveDialog(frame);
		    if(returnVal == JFileChooser.APPROVE_OPTION)
		    {
		    	File file = chooser.getSelectedFile();
		    	if (file == null) return;

		    	if (file.exists())
		    	{
		    		int choice = JOptionPane.showConfirmDialog(frame, "Do you want to overwrite the contents of:\n" + file.getAbsolutePath(), "File Exists", JOptionPane.YES_NO_OPTION);
		    		if (choice == JOptionPane.NO_OPTION)
		    			return;
		    	}

	    		try
	    		{
	    			BufferedWriter out = new BufferedWriter(new FileWriter(file));

	    			Iterator<String> it = MIF.iterator();
	    			while (it.hasNext())
	    			{
	    				String string = it.next();
	    				out.write(string.replaceAll("\n", ""));
						out.write("\r\n");
	    			}

	    			out.close();

	    			JOptionPane.showMessageDialog(frame, "MIF output saved to the file:\n" + file.getAbsolutePath());
	    		}
	    		catch (IOException e)
	    		{
	    			JOptionPane.showMessageDialog(frame, "Unable to open the file:\n" + file.getAbsolutePath());
	    		}
		    }
		}

		/**
		 * Print the error messages from this class's assembler object.
		 *
		 * @return	Returns false if nothing was printed, otherwise returns true.
		 */
		private boolean printErrors()
		{
			if (assembler == null) return false;

			ArrayList<String> errors = assembler.getErrorMessages();
			if (errors.size() != 0)
			{
				output("Errors:");
				for (int i = 0; i < errors.size(); i++)
				{
					String message = errors.get(i);
					output("\t" + message);
				}
				output("\n");
			}

			if (errors.size() == 0)
				return false;
			else
				return true;
		}

		/**
		 * Outputs text into the JTextArea box on the GUI.
		 *
		 * @param text The text to append to the text area box.
		 */
		private void output(String text)
		{
			if (text.endsWith("\n"))
				textArea.append(text);
			else
				textArea.append(text + "\n");

			textArea.setCaretPosition(textArea.getDocument().getLength());
		}

	}
